package com.audaque.springboot.foshanupload.core.util;

import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLParser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import cn.hutool.core.io.file.FileReader;
/**
 * @author zgb
 * @desc ...
 * @date 2023-08-24 18:25:31
 */
@Slf4j
public class FileUtil {

    private static final int BUFFER_SIZE = 1024 * 4;

    public static boolean isWin() {
        return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Win"));
    }

    public static boolean isWin32() {
        return "32".equals(System.getProperty("sun.arch.data.model"));
    }

    public static boolean isMac() {
        return (System.getProperty("os.name") != null && System.getProperty("os.name").startsWith("Mac"));
    }

    /**
     * tranfer yaml file to properties
     * @param path the path of yaml file
     */
    public static void yaml2Prop(String path) {
        List<String> lines = new LinkedList<>();

        // DOT用于隔开key中不同的键
        final String DOT = "*";

        //layerOfArray表示当前数组是第几层数组，默认为0即没有数组；每遍历到"["就增加1
        int layerOfArray = 0;

        // inArrays的索引表示yml文件中遍历到的token位于第几层数组，而元素内容表示当前遍历到的token仍在数组内部，元素默认值为false
        boolean[] inArrays = new boolean[4];
        // arrayIndexs的索引表示yml文件中遍历到的token位于第几层数组，而元素内容表示其对应的索引，元素默认值为0
        int[] arrayIndexs = new int[4];
        // arrayCuteds的索引表示yml文件中遍历到的token位于第几层数组，而元素内容表示 含有中括号的键是否已被切去，元素默认值为false
        boolean[] arrayCuteds = new boolean[4];
        // 注意：上面3个数组，目前均初始化了4个元素值，对应0、1、2、3，表示可以解析最多3层数组嵌套；
        // 若要更多层，修改该初始值即可

        try {
            YAMLFactory yamlFactory = new YAMLFactory();
            YAMLParser parser = yamlFactory.createParser(new InputStreamReader(new FileInputStream(path), Charset.forName("utf-8")));

            String key = ""; //这里的key是最终转换出的properties文件中key，并不是yml文件中的键值对中的键
            String value = null;
            // 先获取到基于json格式的第一个token，便于后面的遍历
            JsonToken token = parser.nextToken();
            while (token != null) {

                // 基于json格式，如果是一个对象开始(即遇到了左花括号"{"时)
                if (JsonToken.START_OBJECT.equals(token)) {
                    // do nothing
                } else if (JsonToken.FIELD_NAME.equals(token)) {   // 基于json格式，如果遇到键值对的键时

                    // 使用点"."分割每层的key
                    if (key.length() > 0) {
                        // 如果该对象在数组中，并且key包含中括号的数量不等于 当前所在数组的层次时，则添加上数组索引
                        if (inArrays[layerOfArray] == true && containNumbers(key, "[") != layerOfArray) {
                            key = key + "[" + arrayIndexs[layerOfArray] + "]";
                        }
                        key = key + DOT;
                    }
                    key = key + parser.getCurrentName();

                    // 继续遍历下一个token
                    token = parser.nextToken();
                    /******************************************************************************************/
                    //如果遇到左中括号"["，表示数组的开始
                    if (JsonToken.START_ARRAY.equals(token)) {
                        // 进入第一层数组
                        layerOfArray++;
                        inArrays[layerOfArray] = true;

                        token = parser.nextToken();
                    }
                    /******************************************************************************************/

                    // 如果遇到子对象的开始(即"{")，则跳入下一个循环
                    if (JsonToken.START_OBJECT.equals(token)) {
                        continue;
                    }

                    /******************************************************************************************/
                    // 此时，当前token遍历到了一个键值对的值时(即到了一个分支尽头)，需要将其放入string数组中
                    value = parser.getText();
                    //如果这个值是单独被包含在中括号数组内(中括号内没有键值对 对应的键)，则key肯定还没有在相应的键上添加索引，所以这里要补上索引
                    if (inArrays[layerOfArray] == true && containNumbers(key, "[") != layerOfArray) {
                        key = key + "[" + arrayIndexs[layerOfArray] + "]";
                    }
                    lines.add(key + "=" + value);

                    /******************************************************************************************/
                    // 每当遍历完一个分支，需要将 key截断到倒数第二个键
                    int dotOffset = key.lastIndexOf(DOT);
                    if (key.length() - 1 == key.lastIndexOf("]")) {
                        arrayCuteds[layerOfArray] = true;
                    }
                    if (dotOffset > 0) {
                        key = key.substring(0, dotOffset);
                    } else {
                        // 若原key中没有"."，则key直接置为""
                        key = "";
                    }


                    // 若截断后剩下的key的最后一个键含有中括号，也就是该键的索引即将更新，则去除掉该中括号子串以便于后面添加新的索引
                    if (key.length() > 0 && key.length() - 1 == key.lastIndexOf("]")) {
                        key = key.substring(0, key.lastIndexOf("["));
                    }

                }else if (JsonToken.END_OBJECT.equals(token)) {    // 基于json格式，如果是一个对象结束(即遇到了右花括号"}"时)

                    // 如果当前token在数组内部，则不需要截断
                    if (inArrays[layerOfArray]) {
                        arrayIndexs[layerOfArray]++;
                    } else {
                        int dotOffset = key.lastIndexOf(DOT);
                        if (dotOffset > 0) {
                            // 若原key中还有"."，则截断到倒数第二个键
                            key = key.substring(0, dotOffset);
                        } else {
                            // 若原key中没有"."，则key直接置为""
                            key = "";
                        }
                    }

                } else if (JsonToken.END_ARRAY.equals(token)) {  //如果遇到右中括号"]"，表示数组的结束
                    // 若当前层次中 含有中括号的键未被切去
                    if (!arrayCuteds[layerOfArray]) {
                        int dotOffset = key.lastIndexOf(DOT);
                        if (dotOffset > 0) {
                            // 若原key中还有"."，则截断到倒数第二个键
                            key = key.substring(0, dotOffset);
                        } else {
                            // 若原key中没有"."，则key直接置为""
                            key = "";
                        }
                    }

                    // 重置该层的变量
                    inArrays[layerOfArray] = false;
                    arrayIndexs[layerOfArray] = 0;
                    arrayCuteds[layerOfArray] = false;

                    // 回退到上一层
                    layerOfArray--;

                    // 若截断后剩下的key的最后一个键含有中括号，也就是上一层中 该键的索引即将更新，则去除掉该中括号子串 以便于后面添加新的索引
                    if (key.length() > 0 && key.length() - 1 == key.lastIndexOf("]")) {
                        key = key.substring(0, key.lastIndexOf("["));
                    }
                }
                token = parser.nextToken();
            }
            parser.close();

            // 将String字符串数组中的内容打印出来
            for (String line : lines) {
                line = line.replace(DOT, ".");
                System.out.println(line);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 计算 字符串str中包含多少个 模式串s
     * @param str 主字符串
     * @param s 模式串
     * @return
     */
    private static int containNumbers(String str, String s) {
        int count = 0;
        for(int i = 0; i < str.length(); ){
            int c = -1;
            c = str.indexOf(s);
            if (c != -1){
                str = str.substring(c + 1);
                count ++;
            } else {
                break;
            }
        }
        return count;
    }


    public void delete(List<File> fileList) {
        for (File file : fileList) {
            cn.hutool.core.io.FileUtil.del(file);
        }
    }

    public static String writeToLocal(String destination, InputStream input)
            throws IOException {
        int index;
        byte[] bytes = new byte[1024];
        FileOutputStream downloadFile = new FileOutputStream(destination);
        while ((index = input.read(bytes)) != -1) {
            downloadFile.write(bytes, 0, index);
            downloadFile.flush();
        }
        downloadFile.close();
        input.close();
        return destination;
    }

    public static void writeFile(InputStream inputStream, File file) throws IOException {
        OutputStream outputStream = null;
        try {
            // 创建输出流
            outputStream = new FileOutputStream(file);
            // 写入文件
            int read = 0;
            byte[] bytes = new byte[1024];
            while ((read = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, read);
            }
        } finally {
            // 关闭输入输出流
            if (inputStream != null) {
                inputStream.close();
            }
            if (outputStream != null) {
                outputStream.close();
            }
        }
    }

    /**
     * 根据地址获得数据的输入流
     * @param strUrl 网络连接地址
     * @return url的输入流
     */
    public static InputStream getInputStreamByUrl(String strUrl){
        HttpURLConnection conn = null;
        try {
            URL url = new URL(strUrl);
            conn = (HttpURLConnection)url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(20 * 1000);
            final ByteArrayOutputStream output = new ByteArrayOutputStream();
            IOUtils.copy(conn.getInputStream(),output);
            return  new ByteArrayInputStream(output.toByteArray());
        } catch (Exception e) {
            log.error(e+"");
        }finally {
            try{
                if (conn != null) {
                    conn.disconnect();
                }
            }catch (Exception e){
                log.error(e+"");
            }
        }
        return null;
    }


    public static void del(File directory, String delDirectoryName) {
        if (!directory.exists()) {
            return;
        }

        if (directory.isDirectory()) {
            String name = directory.getName();
            if (Objects.equals(name, delDirectoryName)) {
                del(directory);
            }else{
                for (File f : directory.listFiles()) {
                    del(f, delDirectoryName);
                }

            }

        }


    }

    static void del(File file) {
        if (!file.exists()) {
            return;
        }
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if(files.length==0){
                file.delete();
            }else{
                for (File f :files ) {
                    del(f);
                    file.delete();
                }
            }

        } else if (file.isFile()) {
            file.delete();
        }
    }

}
